package com.yahoo.labs.snow;

import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.io.FileLinesCollection.FileLinesIterator;

import java.io.File;

import cern.colt.matrix.impl.SparseDoubleMatrix2D;

import com.yahoo.labs.snow.util.FileUtils;
import com.yahoo.labs.snow.util.MatrixUtils;

/**
 * A problem instance created by reading files from a directory.
 * 
 * Compatibility and coverage data about nodes not included in the file of costs are silently
 * skipped.
 * 
 * @author chato
 * 
 */
public class ProblemInstanceFromFiles extends ProblemInstance {

	public ProblemInstanceFromFiles(final String fileCosts, final String fileCompat, final String fileCover, final long budget) {
		this(new File(fileCosts), new File(fileCompat), new File(fileCover), budget);
	}

	public ProblemInstanceFromFiles(final File fileCosts, final File fileCompat, final File fileCover, final long budget) {
		super(budget);

		// Create id<->node mapping from first file
		createIdNodeMappings(fileCosts);

		// Load costs, make sure all costs are positive
		nodeCost = loadInt2Double(fileCosts);
		for (int i = 0; i < nodeCost.size(); i++) {
			if (nodeCost.get(i) <= 0.0) {
				System.err.println("Node costs must be positive, but node " + i + " has cost " + nodeCost.get(i));
				if (i == 0) {
					System.err.println("Hint: make sure node-ids start from 0 and are consecutive");
				}
				throw new IllegalArgumentException("Problem with node costs in " + fileCosts.getAbsoluteFile());
			}
		}

		nodeCompat = loadAndSymmetrizeMatrix(nodeCost.size(), fileCompat);

		nodeCover = loadInt2IntSet(fileCover);
		for (int i = 0; i < nodeCover.size(); i++) {
			if (nodeCover.get(i).size() == 0) {
				System.err.println("All nodes must cover some attribute values, but node " + i + " covers nothing");
				if (i == 0) {
					System.err.println("Hint: make sure node-ids start from 0");
				}
				throw new IllegalArgumentException("Problem with node covers in " + fileCover.getAbsoluteFile());
			}
		}
	}

	private Int2DoubleOpenHashMap loadInt2Double(File file) {
		FileLinesIterator it = FileUtils.getIterator(file);
		Int2DoubleOpenHashMap map = new Int2DoubleOpenHashMap();
		while (it.hasNext()) {
			String[] tokens = (it.next()).toString().split("\t", 2);
			int nodeId = getId(tokens[0]);
			if (nodeId >= 0) { // Skip silently nodes not mentioned in the cost file
				double value = Double.parseDouble(tokens[1]);
				map.put(nodeId, value);
			}
		}
		return map;
	}

	private SparseDoubleMatrix2D loadAndSymmetrizeMatrix(int nElements, File file) {
		FileLinesIterator it = FileUtils.getIterator(file);
		SparseDoubleMatrix2D matrix = new SparseDoubleMatrix2D(nElements, nElements);
		while (it.hasNext()) {
			String line = (it.next()).toString();
			String[] tokens = line.split("\t", 3);
			if (tokens.length == 3) {
				int nodeId1 = getId(tokens[0]);
				int nodeId2 = getId(tokens[1]);
				if (nodeId1 >= 0 && nodeId2 >= 0) { // Skip silently nodes not mentioned in the cost
													// file
					double value = Double.parseDouble(tokens[2]);
					matrix.setQuick(nodeId1, nodeId2, value);
				}
			} else {
				System.err.println( "Ignoring line: '" + line + "' in " + file);
			}
		}
		MatrixUtils.ensureSymmetric(matrix);
		return matrix;
	}

	private Int2ObjectOpenHashMap<IntOpenHashSet> loadInt2IntSet(File file) {
		FileLinesIterator it = FileUtils.getIterator(file);
		Int2ObjectOpenHashMap<IntOpenHashSet> map = new Int2ObjectOpenHashMap<IntOpenHashSet>();
		while (it.hasNext()) {
			String[] tokens = (it.next()).toString().split("\t");
			int nodeId = getId(tokens[0]);
			if (nodeId >= 0) { // Skip silently nodes not mentioned in the cost file
				int[] idsSet = new int[tokens.length - 1];
				for (int i = 0; i < tokens.length - 1; i++) {
					idsSet[i] = Integer.parseInt(tokens[i + 1]);
				}
				if (map.containsKey(nodeId)) {
					map.get(nodeId).addAll(new IntOpenHashSet(idsSet));
				} else {
					map.put(nodeId, new IntOpenHashSet(idsSet));
				}
			}
		}
		return map;
	}
}